package com.example.textcompare.demos.action;


import com.spire.doc.Document;
import com.spire.doc.FileFormat;

import java.io.*;
import java.util.*;

/**
 *  实现两个txt文件的对比
 *  ①输入：1.txt，2.txt所在位置
 *  ②输出两者差异位置
 */

public class ContrastFile {
	//旧文本中差异区域的起点
	static int oldStart = 0;
	//旧文本中差异区域的终点
	static int oldEnd = 0;
	//新文本中差异区域的起点
	static int newStart = 0;
	//新文本中差异区域的终点
	static int newEnd = 0;

	//在寻找一块差异区域的终点时，是否是第一次找到终点
	static boolean isFirstGetEnd = true;

	//存储旧文本的每一行
	static List<String> oldLines;
	//存储新文本的每一行
	static List<String> newLines;

	//下面的集合用来存储对比结果
	//存储增加的行在新文本中的行号
	static List<Integer> delLines = new LinkedList<Integer>();
	//存储删除的行在旧文本中的行号
	static List<Integer> addLines = new LinkedList<Integer>();
	//存储修改的行分别在新旧文本中的行号
	static Map<Integer,Integer> updateLines = new HashMap<Integer,Integer>();

	public static void main(String[] args) throws IOException {
		//1.将docx文件，变为txt文件
		//第一步：1.docx
		//加载Word文档
		Document document1 = new Document();
		document1.loadFromFile("D:\\AAAA\\1.docx");
		//获取文档中的文本保存为String
		String text1=document1.getText();
         //System.out.println(text1);
		//将其另存为docx
		document1.saveToFile("D:\\AAAA\\1.txt", FileFormat.Txt);

		Document document2 = new Document();
		document2.loadFromFile("D:\\AAAA\\2.docx");
		//获取文档中的文本保存为String
		String text2=document2.getText();
        //System.out.println(text2);
		//将其另存为docx
		document2.saveToFile("D:\\AAAA\\2.txt", FileFormat.Txt);

		//作为旧文本
		String path1="D:\\AAAA\\1.txt";
		//作为新文本
		String path2="D:\\AAAA\\2.txt";

		//获取比对结果
		List<String> differMsgList = getContrastResult(path1,path2);

		//打印出对比结果
		for (String differ : differMsgList){
			System.out.println(differ);
		}

	}

	//比对文本，并收集整理对比结果
	public static List<String> getContrastResult(String path1,String path2){
		//读取文件
		oldLines = readFile(path1);
		newLines = readFile(path2);

		//调用对比方法
		compare();

		List<String> differMsgList = new ArrayList<String>();


		for(int lineNum : delLines){
			differMsgList.add("旧文本中删除了第"+(lineNum+1)+"行，内容是:"+oldLines.get(lineNum));
		}
		for(int lineNum : addLines){
			differMsgList.add("新文本中增加了第"+(lineNum+1)+"行，内容是:"+newLines.get(lineNum));
		}

		for(int oldNum : updateLines.keySet()){
			differMsgList.add("旧文本中的第"+(oldNum+1)+"行，内容是:"+oldLines.get(oldNum)
					+"，修改为新文本中的第"+(updateLines.get(oldNum)+1)+"行，内容是:"+newLines.get(updateLines.get(oldNum)));
		}

		return differMsgList;
	}

	//此方法用于读取文件，并且去除空行
	static public List<String> readFile(String path) {

		BufferedReader reader = null;
		File file = new File(path);
		if(!file.exists()) {
			System.out.println("文件不存在");
		}
		String tempStr;
		try {
			reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "utf-8"));

			List<String> lines=new ArrayList<>();
			while ((tempStr = reader.readLine()) != null) {
				//读取文本时，每一行采用行号+行文本内容键值对的形式进行存储，行号作为该行的唯一标识
				if(!tempStr.trim().equals("")){
					lines.add(tempStr);
				}
			}
			return lines;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}finally {
			if(reader!=null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}


	//准备方法，计算两个字符串相同字符的数量
	static public int numJewelsInStones(String J, String S) {
		J=J.trim();
		S=S.trim();
		char[] Ja = J.toCharArray();
		char[] Sa = S.toCharArray();
		int r = 0;
		for (int i = 0;i < Ja.length ; i ++){
			for(int j = 0; j < Sa.length; j++){
				if(Ja[i] == Sa[j])
					r ++;
			}
		}
		return r;
	}


	/**
	 * 逐行对比文本，找到第一个不同或者相同的行
	 * 其中的oldLines和newLines为成员属性，表示旧文本的每一行和新文本的每一行
	 * type：是找第一个内容相同的行还是不同的行，true为找相同，false为找不同
	 * oldLineStart：旧文本从第几行开始
	 * newLineStart：新文本从第几行开始
	 * 返回true表示寻找成功，返回false表示寻找失败
	 */

	static public boolean compareLines(boolean type,int oldLineStart,int newLineStart){
		if(oldLineStart >= oldLines.size() || newLineStart >= newLines.size()){
			return  false;
		}

		//行号计数器
		int lineCount = 0;
		//开始逐行比对两个文本
		int oldLineNumber,newLineNubmer;
		while ((oldLineNumber=oldLineStart+lineCount)<oldLines.size() && (newLineNubmer=newLineStart+lineCount)<newLines.size()){
			//分别取出新旧文本中的一行
			String lineOld = oldLines.get(oldLineNumber);
			String lineNew = newLines.get(newLineNubmer);


			//下面代码中的oldEnd、oldStart、newEnd、newStart为实例属性，
			//分别表示旧文本差异区域的起终点和新文本差异区域的起终点

			//找到完全相同的两行，其可以作为差异区域的终点
			if(type && lineOld.equals(lineNew)){
				//如果是第一次找到终点，先记录在oldEnd、newEnd两个属性中
				if(isFirstGetEnd){
					oldEnd = oldLineNumber;
					newEnd = newLineNubmer;
					isFirstGetEnd = false;
					//如果不是第一次找到，比较哪个终点与起点最近，取最近的终点
				}else if(newLineNubmer<newEnd){
					oldEnd = oldLineNumber;
					newEnd = newLineNubmer;
				}
				return true;
			}
			//找到差异的两行，其可以作为差异区域的起点
			if(!type && !lineOld.equals(lineNew)){
				oldStart = oldLineNumber;
				newStart = newLineNubmer;
				return true;
			}
			lineCount++;
		}
		//到文本的最后还没找到，返回false
		return false;
	}


	//在新旧文本寻找差异区域的起点，oldLines和newLines分别为存储新旧文本行内容的Map集合
	static public boolean getDifferenceAreaStart() {
		return compareLines(false,oldEnd,newEnd);
	}


	//寻找差异区域的终点，也就是新旧文本重新复合的点。
	static public boolean getDifferenceAreaEnd() {
		//重置为true
		isFirstGetEnd = true;
		//标记是否找到终点
		boolean haveEnd = false;
		//moveLines为文本下移的行数
		int moveLines = 0;
		int oldLineNumber=oldStart,newLineNubmer=newStart;
		while ((oldLineNumber<oldLines.size() || newLineNubmer < newLines.size())
		){
			//newStart为0时不移动文本，newStart大于0时尝试以移动文本的方式来找终点
			if(compareLines(true,oldLineNumber,newStart) || compareLines(true,oldStart,newLineNubmer)){
				haveEnd = true;
			}
			moveLines ++;
			oldLineNumber = oldStart + moveLines;
			newLineNubmer = newStart + moveLines;
		}
		return haveEnd;
	}

	//找出差异区域内哪些是修改的行
	//参数n表示我们需要找的修改前后的行有几对
	public static Map<Integer, Integer> getUpdateLines(int n) {

		Map<Integer, Integer> resultMap=new HashMap();

		//准备集合，用来储存组队两行的重复字符个数与各自的行号
		SortedMap<Integer,String> samCharMap = new TreeMap<Integer, String>(new Comparator<Integer>() {
			@Override
			public int compare(Integer number1, Integer number2) {
				//从大到小排序
				return number2 - number1;
			}
		});

		for(int i = oldStart; i < oldEnd ; i++){
			for(int j = newStart ; j <newEnd ;j++){
				int count=numJewelsInStones(oldLines.get(i),newLines.get(j));
				samCharMap.put(count,String.valueOf(i)+":"+String.valueOf(j));
			}
		}

		int i=0;
		String lineInfo;
		Iterator<Integer> it = samCharMap.keySet().iterator();
		while (i<n && it.hasNext()){
			lineInfo = samCharMap.get(it.next());
			String[] lineNumA=lineInfo.split(":");
			resultMap.put(Integer.valueOf(lineNumA[0]),Integer.valueOf(lineNumA[1]));
			i++;
		}
		return resultMap;
	}


	//分析文本的变化类型，存入结果集合中
	public static void analChangeType() {

		//下面开始分析差异区域的变化类型，然后按照类型进行处理
		//oldEnd、oldStart、newEnd、newStart为实例属性，
		//分别表示旧文本差异区域的起终点和新文本差异区域的起终点
		int oldNumDiff = oldEnd-oldStart;
		int newNumDiff = newEnd-newStart;

		//纯修改
		if(oldNumDiff == newNumDiff){
			int number=oldEnd-oldStart;
			for(int i = 0 ;i<number ;i++){
				updateLines.put(oldStart+i,newStart+i);
			}
		}else if(oldNumDiff > newNumDiff){
			if(newEnd==newStart){
				//纯删除
				for(int i=oldStart;i<oldEnd;i++) {
					//取出被删除的行，存入集合
					delLines.add(i);
				}
			}else {
				//删除加修改
				//计算修改的行数
				int updateNum=newNumDiff;
				//获取修改的行，getUpdateLines为获取修改行对于关系的方法，
				// 返回的Map中存储的是修改前后的行号
				Map<Integer, Integer> changeLineMap=getUpdateLines(updateNum);
				updateLines.putAll(changeLineMap);
				//获取删除的行
				for(int lineNum = oldStart ;lineNum <oldEnd ; lineNum ++){
					if(!changeLineMap.containsKey(lineNum)){
						delLines.add(lineNum);
					}
				}
			}
		}else {
			if(oldEnd==oldStart){
				//纯新增
				for(int i=newStart;i<newEnd;i++) {
					addLines.add(i);
				}
			}else {
				//新增加修改
				//此时修改的行数是：
				int number=oldNumDiff;
				//获取修改的旧文本行号与新文本行号组成键值对的集合
				Map<Integer, Integer> changeLineMap = getUpdateLines(number);
				updateLines.putAll(changeLineMap);
				//获取新增的行
				for(int lineNum = newStart ;lineNum <newEnd ; lineNum ++){
					if(!changeLineMap.values().contains(lineNum)){
						addLines.add(lineNum);
					}
				}
			}
		}
	}



	//递归对比文本
	public static void compare(){
		//如果能找到差异区域的起点
		if(getDifferenceAreaStart()){
			//也能找到差异区域的终点
			if(getDifferenceAreaEnd()){
				analChangeType();
				compare();
			}else {
				//如果找不到差异区域的终点，说明从起点开始下文全是差异区域
				oldEnd = oldLines.size();
				newEnd = newLines.size();
				analChangeType();
			}
		}
	}
}


